/*
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * Copyright (C) 2012  Joshua M. Clulow <josh@sysmgr.org>
 */


import crypto from 'crypto';
import $ from './common.js'
import {lmhashbuf, getNtHash} from './smbhash.js'


let exports = {
  encodeType1 : encodeType1,
  decodeType2 : decodeType2,
  encodeType3 : encodeType3
}


function encodeType1() {
  let hostname = "";// hostname.toUpperCase();
  let ntdomain = "";//ntdomain.toUpperCase();
  var hostnamelen = Buffer.byteLength(hostname, 'ascii');
  var ntdomainlen = Buffer.byteLength(ntdomain, 'ascii');

  var pos = 0;
  var buf = new Buffer(32 + hostnamelen + ntdomainlen);

  // --- Signature (8 bytes) ---
  buf.write('NTLMSSP', pos, 7, 'ascii'); // byte protocol[8];
  pos += 7;
  buf.writeUInt8(0, pos);
  pos++;

  // --- MessageType (4 bytes) ---
  buf.writeUInt8(0x01, pos); // byte type;
  pos++;

  buf.fill(0x00, pos, pos + 3); // byte zero[3];
  pos += 3;

  // --- NegotiateFlags (4 bytes) ---
  buf.writeUInt32LE(0x00000001, pos); // byte zero[2];
  pos += 4;

  // --- DomainNameLen (2 bytes) ---
  buf.writeUInt16LE(ntdomainlen, pos); // short dom_len;
  pos += 2;

  // --- DomainNameMaxLen (2 bytes) ---
  buf.writeUInt16LE(ntdomainlen, pos); // short dom_len;
  pos += 2;

  // --- DomainNameBufferOffset (4 bytes) ---
  var ntdomainoff = 0x20 + hostnamelen;
  buf.writeUInt16LE(ntdomainoff, pos); // short dom_off;
  pos += 2;

  buf.fill(0x00, pos, pos + 2); // byte zero[2];
  pos += 2;

  // --- WorkstationLen (2 bytes) ---
  buf.writeUInt16LE(hostnamelen, pos); // short host_len;
  pos += 2;

  // --- WorkstationMaxLen (2 bytes) ---
  buf.writeUInt16LE(hostnamelen, pos); // short host_len;
  pos += 2;

  // --- WorkstationBufferOffset (4 bytes) ---
  buf.writeUInt16LE(0x20, pos); // short host_off;
  pos += 2;

  buf.fill(0x00, pos, pos + 2); // byte zero[2];
  pos += 2;

  // --- Payload (variable) ---
  buf.write(hostname, 0x20, hostnamelen, 'ascii');
  buf.write(ntdomain, ntdomainoff, ntdomainlen, 'ascii');

  return buf;
}


function decodeType2(buf)
{
  var proto = buf.toString('ascii', 0, 7);
  if (buf[7] !== 0x00 || proto !== 'NTLMSSP')
    throw new Error('magic was not NTLMSSP');

  var type = buf.readUInt8(8);
  if (type !== 0x02)
    throw new Error('message was not NTLMSSP type 0x02');

  //var msg_len = buf.readUInt16LE(16);

  //var flags = buf.readUInt16LE(20);

  var nonce = buf.slice(24, 32);
  return nonce;
}

function getV2Response(nonce, userName, password) {
  //生成8字节 Client Challenge
  var clientChallenge = new Buffer.allocUnsafe(8);
  clientChallenge.fill(0x99, 0);


  // Set temp to ConcatenationOf(Responserversion, HiResponserversion, Z(6), Time(8), ClientChallenge(8), Z(4), ServerName(0), Z(4))
 
  let tmp = Buffer.alloc(1+1+6+8+8+4+0+4)

  tmp[0] = 1
  tmp[1] = 1
  let pos = 2
  tmp.fill(0, pos, pos + 6)
  pos += 6

  //前4字节写入秒时间戳
  tmp.writeUInt32LE(0xe47d6f80, pos); // byte type;
  pos += 4;
  tmp.writeUInt32LE(0x01d77415, pos); // byte type;
  pos += 4;

  clientChallenge.copy(tmp, pos, 0)
  pos += 8

  tmp.fill(0, pos, pos + 4)
  pos += 4

  tmp.fill(0, pos, pos + 4)
  pos += 4

  let responseKeyNT = NTOWFv2(password, userName)

  //   Set NTProofStr to HMAC_MD5(ResponseKeyNT,ConcatenationOf(CHALLENGE_MESSAGE.ServerChallenge,temp))
  let challeng = contactBuff([nonce, tmp])
  let hmac = crypto.createHmac('md5', responseKeyNT)
  let up = hmac.update(challeng)
  let NTProofStr = new Buffer.from(up.digest('binary'), 'binary') 

  //  Set NtChallengeResponse to ConcatenationOf(NTProofStr, temp)
  let NtChallengeResponse = contactBuff([NTProofStr, tmp])


  // Set SessionBaseKey to HMAC_MD5(ResponseKeyNT, NTProofStr)
  let hmac1 = crypto.createHmac('md5', responseKeyNT)
  let up1 = hmac1.update(NTProofStr)
  let SessionBaseKey = new Buffer.from(up1.digest('binary'), 'binary') 


  return {nt: NtChallengeResponse, lm: Buffer.alloc(0), sessionBaseKey: Buffer.alloc(0)} 
}

function contactBuff(arr){
  let length = 0
  arr.map(v=>{
    length += v.length
  })

  let buff = Buffer.alloc(length)
  let offset = 0
  arr.map(v=>{
    v.copy(buff, offset)
    offset += v.length
  })
  return buff
}


// Define NTOWFv2(Passwd, User, UserDom) as HMAC_MD5(
//   MD4(UNICODE(Passwd)), UNICODE(ConcatenationOf( Uppercase(User),
//   UserDom ) ) )
//   EndDefine

function NTOWFv2(passwd, userName){
  let ntHash = getNtHash(passwd)
  let nameBuff = new Buffer.from(userName.toUpperCase(), 'ascii');

  // 创建一个hmac对象
  const hmac = crypto.createHmac('md5', ntHash)
  const up = hmac.update(nameBuff)
  return new Buffer.from(up.digest('binary'), 'binary') 
}


function getV1Response (password, nonce, extendSession = false) {
  //  生成ntHash
  let ntHash = new Buffer.alloc(21);
  getNtHash(password).copy(ntHash);
  ntHash.fill(0x00, 16); // 填充0，使得NT hash变成21字节
  let lmResponse, ntResponse
  if (extendSession) {
    // 如果协商使用 NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY，lmResponse的内容为 clientChallenge
    
    //生成8字节 Client Challenge
    var clientChallenge = new Buffer.allocUnsafe(8);
    clientChallenge.fill(0x99, 0);

    lmResponse = new Buffer.allocUnsafe(24);
    clientChallenge.copy(lmResponse, 0)
    lmResponse.fill(0x00, 8);

    ntResponse = makeResponse1(clientChallenge, nonce, ntHash)
  }else{
    // 生成lmHash
    let lmHash = new Buffer.alloc(21);
    lmhashbuf(password).copy(lmHash);
    lmHash.fill(0x00, 16); // 填充0，使得LM hash变成21字节

    lmResponse = makeResponse(lmHash, nonce);
    ntResponse = makeResponse(ntHash, nonce);
  }
  return {
    lm: lmResponse,
    nt: ntResponse,
    sessionBaseKey: Buffer.alloc(0)
  }
}

function encodeType3(nonce, userName, password) {
  let isV1 = false


  let hostName = "".toUpperCase();
  let domain = "".toUpperCase();

  let responseArr
  if (isV1) {
    responseArr = getV1Response(password, nonce, false)
  }else{
    responseArr = getV2Response(nonce, userName, password)
  }
  
  let lmResponse = responseArr.lm
  let ntResponse = responseArr.nt
  let sessionBaseKey = responseArr.sessionBaseKey




  let listData = {
    userName: {
      length: Buffer.byteLength(userName, 'ucs2'),
      data: Buffer.from(userName, 'ucs2'),
    
    },
    hostName: {
      length: Buffer.byteLength(hostName, 'ucs2'),
      data: Buffer.from(hostName, 'ucs2'),
    },
    domain: {
      length: Buffer.byteLength(domain, 'ucs2'),
      data: Buffer.from(domain, 'ucs2'),
    },
    encryptedRandomSession:{
      length: sessionBaseKey.length,
      data: sessionBaseKey,
    },
    lmResponse: {
      length: lmResponse.length,
      data: lmResponse,
    },
    ntResponse: {
      length: ntResponse.length,
      data: ntResponse,
    }
  }
  let listKey = ['userName', 'hostName', 'domain', 'lmResponse', 'ntResponse', 'encryptedRandomSession'];

  // Signature(8) + MessageType(4) + LmChallengeResponse(8) + NtChallengeResponse(8) + DomainName(8) + UserName(8) + WorkstationLen(8) + EncryptedRandomSessionKey(8) + NegotiateFlags(4) + IMC(16)
  let offset = 80
  listKey.map(key=>{
    listData[key].offset = offset
    offset += listData[key].length
  })

  var buf = new Buffer.alloc(offset);

  var pos = 0;
  // --- Signature (8 bytes) ---
  buf.write('NTLMSSP', pos, 7, 'ascii'); // byte protocol[8];
  pos += 7;
  buf.writeUInt8(0, pos);
  pos++;

  // --- MessageType (4 bytes) ---
  buf.writeUInt32LE(0x00000003, pos); // byte type;
  pos += 4;

  // --- LmChallengeResponse (8 bytes) ---
  buf.writeUInt16LE(listData.lmResponse.length, pos); 
  pos += 2;
  buf.writeUInt16LE(listData.lmResponse.length, pos); 
  pos += 2;
  buf.writeUInt32LE(listData.lmResponse.offset, pos);
  pos += 4;

  // --- NtChallengeResponse (8 bytes) ---
  buf.writeUInt16LE(listData.ntResponse.length, pos); 
  pos += 2;
  buf.writeUInt16LE(listData.ntResponse.length, pos);
  pos += 2;
  buf.writeUInt32LE(listData.ntResponse.offset, pos); 
  pos += 4;

  // --- DomainName (8 bytes) ---
  buf.writeUInt16LE(listData.domain.length, pos); 
  pos += 2;
  buf.writeUInt16LE(listData.domain.length, pos);
  pos += 2;
  buf.writeUInt32LE(listData.domain.offset, pos); 
  pos += 4;


  // --- UserName (8 bytes) ---
  buf.writeUInt16LE(listData.userName.length, pos);
  pos += 2;
  buf.writeUInt16LE(listData.userName.length, pos); 
  pos += 2;
  buf.writeUInt32LE(listData.userName.offset, pos);
  pos += 4;


  // --- WorkstationLen (8 bytes) ---
  buf.writeUInt16LE(listData.hostName.length, pos);
  pos += 2;
  buf.writeUInt16LE(listData.hostName.length, pos);
  pos += 2;
  buf.writeUInt32LE(listData.hostName.offset, pos);
  pos += 4;


  // --- EncryptedRandomSessionKey (8 bytes) ---
  buf.writeUInt16LE(listData.encryptedRandomSession.length, pos);
  pos += 2;
  buf.writeUInt16LE(listData.encryptedRandomSession.length, pos);
  pos += 2;
  buf.writeUInt32LE(listData.encryptedRandomSession.offset, pos)
  pos += 4

  // --- NegotiateFlags (4 bytes) ---
  //W V U r1 r2 r3 T r4
  //S R r5 Q P r6 O N 
  //M r7 L K J r8 H r9
  //G F E D r10 C B A
  buf.writeUInt32LE(0x00000003, pos); 
  pos += 4

  // IMC 填充16字节
  buf.fill(0x00, pos, pos + 16);
  pos += 16
  
  listKey.map(key=>{
    let item = listData[key]
    item.data.copy(buf, item.offset, 0, item.length);
  })
  return buf;
}



function makeResponse1(clientChallenge, serverChallenge, ntHash)
{
 

  //拼接Server Challenge和Client Challenge
  var csChallenge = new Buffer.allocUnsafe(16);

  serverChallenge.copy(csChallenge, 0, 0);
  clientChallenge.copy(csChallenge, 8, 0);


  var md5 = crypto.createHash('md5');
  md5.update(csChallenge);
  //md5Res 16字节
  let md5Res = new Buffer.from(md5.digest('binary'), 'binary');

  // 获取前8字节
  let md5Cut = md5Res.slice(0, 8);

  var out = new Buffer(24);
  for (var i = 0; i < 3; i++) {
    var keybuf = $.oddpar($.expandkey(ntHash.slice(i * 7, i * 7 + 7)));
    var des = crypto.createCipheriv('DES-ECB', keybuf, '');
    var str = des.update(md5Cut.toString('binary'), 'binary', 'binary');
    out.write(str, i * 8, i * 8 + 8, 'binary');
  }
  return out;

}


function makeResponse(hash, nonce)
{
  var out = new Buffer(24);
  for (var i = 0; i < 3; i++) {
    var keybuf = $.oddpar($.expandkey(hash.slice(i * 7, i * 7 + 7)));
    var des = crypto.createCipheriv('DES-ECB', keybuf, '');
    var str = des.update(nonce.toString('binary'), 'binary', 'binary');
    out.write(str, i * 8, i * 8 + 8, 'binary');
  }
  return out;
}



// Convenience methods.

exports.challengeHeader = function (hostname, domain) {
  return 'NTLM ' + exports.encodeType1(hostname, domain).toString('base64');
};

exports.responseHeader = function (res, url, domain, username, password) {
  var serverNonce = new Buffer((res.headers['www-authenticate'].match(/^NTLM\s+(.+?)(,|\s+|$)/) || [])[1], 'base64');
  var hostname = require('url').parse(url).hostname;
  return 'NTLM ' + exports.encodeType3(username, hostname, domain, exports.decodeType2(serverNonce), password).toString('base64')
};

// Import smbhash module.

export default exports
